#include "ColorMap.h"
#include "Color.h"
#include <algorithm>
#include <GL/glut.h>
#include <cmath>

#include <iostream>

using namespace std;

ColorMap::ColorMap()
:
    d_scalar_col(BlackWhite),    
    d_scale(AUTO),
    d_levels(100),
    d_min(0.0),
    d_max(1.0),
    d_clampMin(0.0),
    d_clampMax(1.0),
    d_h(0.0),
    d_s(1.0),
    d_v(1.0)
{    
    d_colormode_names = {"Black and White", "Rainbow", "Ocean", "Custom Hue", "Circular Rainbow", "Constant"};
    d_scalemode_names = {"Auto", "Clamped"};
    buildMap();
}


void ColorMap::set(double vy, double alpha)
{
    // float R,G,B;
    
    if (d_scale == AUTO)
    {
        vy = (vy - d_min) / (d_max - d_min);
    }
    else if (d_scale == MANUAL)
    {
        vy = (vy - d_clampMin) / (d_clampMax - d_clampMin);    
    }
    
    vy *= d_levels;
    
    int colIdx = floor(vy);
    colIdx = min(d_levels - 1, colIdx);
    colIdx = max(colIdx, 0);
    set(colIdx, alpha);
}

void ColorMap::set(int idx, double alpha)
{    
    Color col = d_map[idx];
    if (d_constH)
        col.changeHue(d_h);
    if (d_constS)
        col.changeSat(d_s);
    if (d_constV)
        col.changeVal(d_v);
    glColor4f(col.r(), col.g(), col.b(), alpha);
}

void ColorMap::rainbow(float value, float* R, float* G, float* B)
{
    const float dx = 0.8;	
    value = (value < 0) ? 0 : (value > 1) ? 1 : value; // crop value to [0,1];
    value = (6 - 2 * dx) * value + dx;                 // scale value to [dx, 6 - dx]
    *R = max(0.0, (3-fabs(value-4)-fabs(value-5)) / 2.0);
    *G = max(0.0, (4-fabs(value-2)-fabs(value-4)) / 2.0);
    *B = max(0.0, (3-fabs(value-1)-fabs(value-2)) / 2.0);
}

void ColorMap::ocean(float value, float* R, float* G, float* B) 
{
    const float dx=0.6;	
    value = (value < 0) ? 0 : (value > 1) ? 1 : value; // crop value to [0,1];
    value = (5 - 2*dx) * value + dx;                 // scale value to [dx, 4 - dx]
    
    *R = max(0.0, (3-fabs(value-4)-fabs(value-5)) / 2.0);
    *G = max(0.0, (4-fabs(value-3)-fabs(value-5)) / 2.0);
    *B = max(0.0, (5-fabs(value-2)-fabs(value-5)) / 2.0);
}

fftw_real ColorMap::usedMax()
{
    fftw_real retVal = d_max;
    if (d_scale == MANUAL)
        retVal = d_clampMax;
    return retVal;
}

fftw_real ColorMap::usedMin()
{
    fftw_real retVal = d_min;
    if (d_scale == MANUAL)
        retVal = d_clampMin;
    return retVal;
}

void ColorMap::buildMap()
{
    d_map.clear();
    buildColVec();
    float dv = 1.0 / (d_levels - 1);
    float dc = float(d_cols.size() - 1) / (d_levels - 1);
    
    for (int lvl = 0; lvl != d_levels; ++lvl)
    {        
        int colIdx1 = floor(dv * lvl * (d_cols.size() - 1));
        int colIdx2 = ceil(dv * lvl * (d_cols.size() - 1));
        
        //weight factor of first color
        float dum;
        float w1 = 1.0 - modf(lvl * dc, &dum);
        
        //workaround for roundoff problem
        if (w1 == 1.0 && colIdx1 != colIdx2)
            colIdx1 = colIdx2;
        
        d_map.push_back(interColor(colIdx1, colIdx2, w1));
    }
    
}

Color ColorMap::interColor(int id1, int id2, float w1)
{
    float w2 = 1 - w1;
    
    float h1 = d_cols[id1].h();
    float h2 = d_cols[id2].h();
    
    float h  = w1 * h1 + w2 * h2;
    
    if (max(h1,h2) - min(h1,h2) > 0.5)
    {        
        if (h1 > h2)
            h = w1 * h1 + w2 * (h2 + 1);            
        else
            h = w1 * (h1 + 1) + w2 * h2;
        float dum;
        h = modf(h, &dum);
    }
    
    float s  = w1 * d_cols[id1].s() + w2 * d_cols[id2].s();
    float v  = w1 * d_cols[id1].v() + w2 * d_cols[id2].v();
    
    return Color(h, s, v);
}

void ColorMap::buildColVec()
{   
    d_cols.clear();
    switch(d_scalar_col)
    {
        case BlackWhite:
            d_cols = {Color(0.0, 0.0, 0.0), Color(0.0, 0.0, 1.0)};
            d_constH = false;
            d_constS = false;
            d_constV = false;
            break;
        case Rainbow:
            d_cols = {Color(0.67, 1.0, 1.0), Color(0.33, 1.0, 1.0), Color(0.0, 1.0, 1.0)};
            d_constH = false;
            d_constS = true;
            d_constV = true;
            break;
        case Ocean:
            d_cols = {Color(0.67, 1.0, 0.0), Color(0.67, 1.0, 1.0), Color(0.5, 1.0, 1.0), Color(0.5, 0.0, 1.0)};
            d_constH = false;
            d_constS = false;
            d_constV = false;
            break;
        case CustomHue:
            d_cols = {Color(0.0, 1.0, 0.0), Color(0.0, 1.0, 1.0)};
            d_constH = true;
            d_constS = true;
            d_constV = false;
            break;
        case CircularRainbow:
            d_cols = {Color(1.0, 1.0, 1.0), Color(0.25, 1.0, 1.0), Color(0.5, 1.0, 1.0), Color(0.75, 1.0, 1.0), Color(1.0, 1.0, 1.0)};
            d_constH = false;
            d_constS = true;
            d_constV = true;
            break;
        case Constant:
            d_cols = {Color(1.0, 1.0, 1.0)};
            d_constH = true;
            d_constS = true;
            d_constV = true;
            break;
        default:
            break;
    }
}

void ColorMap::levels(int l)
{
    d_levels = l;
    buildMap();
}

void ColorMap::colorMode(int mod)
{
    d_scalar_col = ColorMode(mod);    
    buildMap();
}